"""
AUTHOR: CASSWONG
VERSION: 0.3
SIMPLE WRAPPER TO USE GNS USER MODULE HOSTED ON AWS
"""
from datetime import timedelta
import requests
import json
import os
import time
class GnsuserAPI:
    
    __POST = "POST"
    __GET =   "GET"
    __HEAD = "HEAD"
    __cache_backend = False
    __connection = False
    class _ENDPOINTS:
        getAll = "/getAll"
        getItemByID = "/getItemByID"
        getItemByUsername="/getItemByUsername"
        postItem = "/postItem"
        updateItem ="/updateItem"
        copyUserFromOtherService = "/copyUserFromOtherService"
        root="/"

    def __init__(self, cache_backend=False,DEBUG=False,age_limit=60,save_local_cache=False):
        self.token = "8a425eb38783fc12b946e03a48f6fe2418a76569c5cc3caf1305a242d9f06c46"
        self.host = "https://04nyf8bnxd.execute-api.ap-northeast-1.amazonaws.com"
        if DEBUG:
            print("DEBUG Mode...")
            self.token = "6543660d07424528d9b47a4a2386d25e4c7689678ca3fd5a16653f2e633db4fc"
            self.host = "https://msv9hx25gb.execute-api.ap-northeast-1.amazonaws.com"
        self.__connection = self.testConnection()

        if not self.__connection:
            print("Cannot establish connection")

        if cache_backend:
            self.__cache_backend = SimpleCache(gns=self,age_limit=age_limit)
            
        # if cache_backend:
        #     if isinstance(cache_backend,SimpleCache):
        #         self.__cache_backend = cache_backend

        # if not self.__cache_backend:
        #     self.__cache_backend = SimpleCache(gns=self,age_limit=age_limit)

    def __getHeaders(self):
        return {
            'user-agent': "gnsuser-py-restclient",
            'authorization': self.token,
            'content-type': "application/json"
        }

    def testConnection(self):
        response = self.sendRequest(self.__HEAD,self._ENDPOINTS.root)
        self.__connection=response.status_code in [200,201,203]
        return self.__connection

    def sendRequest(self,method,endpoint,data={}):
        url = self.host+endpoint
        headers = self.__getHeaders()
        response = requests.request(method, url,data=data, headers=headers, timeout=1.5)
        
        return response

    def getAll(self,no_cache=False):
        if self.useCache(no_cache):
            return self.__cache_backend.cache,False
        
        print("Fetching from AWS...")
        response =  self.sendRequest(self.__GET,self._ENDPOINTS.getAll)
        data = json.loads(response.text)
        if self.__cache_backend:
            # if self.__cache_backend.isCacheExp:
            self.__cache_backend.updateCache(data)

        return data,response

    def getItemByID(self,qrcode,no_cache=False):
        if self.useCache(no_cache):
            return self.__cache_backend.get(qrcode),False

        print("Fetching from AWS...")
        response = self.sendRequest(self.__GET,self._ENDPOINTS.getItemByID+f"?qrcode={qrcode}")                
        data = json.loads(response.text)
        return data,response


    def createItem(self,app_name,data):
        """
            pass data as a dictionary 
            {
                'userid':4,
                'username':"",
                'first_name':'',
                'last_name':'',
                'is_active':True,
                'is_superuser':False,
                'is_staff':False,
                'email':'',
                'profile':{
                    "staffcode":'A',
                    "usergp_ids":[1,4]
                },
                'django_groups':[5,6],
                "key":"value",
                ...
            }
        """
        if not app_name or app_name=="":
            raise ValueError("No app_name is provided")
        
        
        payload ={
            "app_name" :app_name,
            "data" :data
        } 

        return self.sendRequest(self.__POST,self._ENDPOINTS.postItem,json.dumps(payload))

    def updateItem(self,qrcode,app_name,data):
        """
            pass data as a dictionary 
            {
                'userid':4,
                'username':'',
                'first_name':'',
                'last_name':'',
                'is_active':True,
                'is_superuser':False,
                'is_staff':False,
                'email':'',
                'profile':{
                    "staffcode":'A',
                    "usergp_ids":[1,4]
                },
                'django_groups':[5,6],
                "key":"value",
                ...
            }
        """

        assert qrcode != None
        assert qrcode != ""
        assert app_name != "" and app_name != None

        payload ={
            "qrcode":qrcode,
            "app_name":app_name,
            "data" :data
        } 
                
        return self.sendRequest(self.__POST,self._ENDPOINTS.updateItem,json.dumps(payload))

    def copyUserFromOtherService(self,qrcode,src_app_name,dest_app_name):
        """
            Make a copy of src_app_name data to dest_app_name that under the qrcode key
            @params {String} qrcode, The primary key of the table (hash) 
            @params {String} src_app_name, the app_name to be copied
            @params {String} dest_app_name, the app_name copy to
        """
        data = {
            "qrcode":qrcode,
            "dest_app_name":dest_app_name,
            "src_app_name":src_app_name,
        }
        return self.sendRequest(self.__POST,self._ENDPOINTS.copyUserFromOtherService,json.dumps(data))
        ## use other service information to create user

    def updateCache(self):
        self.__cache_backend.updateCacheFromAPI()
        
    def createAllFromOtherService(self):
        ## batch way to create user
        pass

    def useCache(self, no_cache=False):
        
        if no_cache:
            print("NO_CACHE==TRUE")
            return False

        if not self.__cache_backend:
            print("No cahce_backend initialized")
            return False

        if self.__cache_backend.cache == {}:
            print("Cache is empty")
            return False

        
        if self.__cache_backend.isCacheExp and self.connection == True:
            print("Cache is exp, having connection, use connection instead")
            return False
        
        if self.__cache_backend.isCacheExp and self.connection == False:
            print("Cache exp but no connection, keep using cache, you may want to call testConnection() to update the connection status or call getAll(no_cache=True) to perform force update")
            return True
        
        return True
    
    def getItemByUsername(self,username):
        response = self.sendRequest(self.__POST,self._ENDPOINTS.getItemByUsername,json.dumps({"username":username}))
        data = json.loads(response.text)
        return data,response

    def findQrcodeByUser(self,username="",userid=False,no_cahce=False):
        """
            Find qrcode by matching username and userid 
        """
        if self.useCache(no_cahce):
            data = [self.__cache_backend.getByUsername(username)]
        else:
            data,response = self.getItemByUsername(username)
            if response and  response.status_code not in [201,200]:
                print(f"Response error, status code:{response.status_code}")
                return False

        for r in data:
            if "username" in r and username==r['username']:
                try:
                    if userid==False:
                        return r['qrcode']
                    if userid in [ value['userid'] for key,value in r['services'].items()]:
                        return r['qrcode']
                except Exception as ex:
                    print(r)
                    print('data has no userid')
                    print(ex)
                    continue
        
        return False

    @property
    def connection(self):
        return self.__connection

class SimpleCache:
    
    __simpleCache = []
    __cache_file = "gnsusermodule.json"
    __age_limit = 60 ## mintues 
    __cache_age = False
    __index = {}
    __username_index = {}
    def __init__(self, gns,age_limit=60):
        self.__gns = gns
        self.__age_limit = age_limit
        self.__initCache()
    
    def __saveCacheAsJson(self):
        try:
            j_str = json.dumps(self.__simpleCache)
            jsonFile = open(self.__cache_file,"w")
            jsonFile.write(j_str)
            jsonFile.close()
            return True
        except Exception as ex:
            print(ex)
            print("cannot save cache")
            return False
    
    def __initCache(self,force_update=False):
        connection =  self.__gns.testConnection()

        if force_update:
            if connection:
                print("Fetching data from AWS...")
                data,_ = self.__gns.getAll(no_cache=True)
                # print(data)
                self.updateCache(data)
                return
            else:
                print("Force Update failed, No Connection to AWS")

        if self.__isLocalCacheExists():
            
            print("Cache found")

            if self.__isLocalCacheExp():
                print("Cache expired, tring to fetch from AWS")
                if connection:
                    print("Fetching data from AWS...")
                    data,_ = self.__gns.getAll(no_cache=True)
                    # print(data)
                    self.updateCache(data)
                    return
                else:
                    print("No connection from AWS")
            else:
                ## cache not exp
                print("Cache not expired")
                print("Load from local cache")
                self.__initCacheLocal()
                return
            
        else:
            ## no local cache 
            print("No local cache found")
            if connection:
                print("Fetching data from AWS...")
                data,_ = self.__gns.getAll(no_cache=True)
                # print(data)
                self.updateCache(data)
                return
            
            if not connection:
                print("No local cahce and cannot esatablish connection")


        return False
    

    def __localCacheAge(self):
        if self.__isLocalCacheExists():
            return os.path.getmtime(self.__cache_file)
        return False
    
    def __isLocalCacheExists(self):
        return os.path.exists(self.__cache_file)

    def __isLocalCacheExp(self):
        mtime = self.__localCacheAge()
        if not mtime:
            return True
        now = time.time()
        limit = now - self.__age_limit * 60 
        if mtime < limit:
            return True
        return False


    def __initCacheLocal(self):
        jsonObject = False
        try:
            with open(self.__cache_file) as jsonFile:
                jsonObject = json.load(jsonFile)
                jsonFile.close()
            self.updateCache(jsonObject)
            return True
        except Exception as ex:
            print(ex)
            self.__simpleCache=[]
            return False
        
    def set(self,qrcode,val):
        key = self.__getKeyFromIndex(qrcode)
        if not key:
            return False
        self.__simpleCache[key] = val
        return True

    def get(self,qrcode):
        key = self.__getKeyFromIndex(qrcode)
        if key==False and isinstance(key,bool):
            return False
        return self.__simpleCache[key]

    def getByUsername(self,username):
        key = self.__getKeyFromUsernameIndex(username)
        try:
            return self.__simpleCache[key]
        except Exception as ex:
            print("key not found")
   
    @property
    def cache(self):
        return self.__simpleCache

    @property
    def cache_age(self):
        return self.__cache_age
    
    @property
    def isCacheExp(self):
        if not self.cache_age:
            return True
        now = time.time()
        limit = now - self.__age_limit * 60 
        if self.cache_age < limit:
            print("cache is exp")
            return True
        return False

    def createIndex(self):
        self.__index = {}
        self.__username_index = {}
        for i,e in enumerate(self.__simpleCache):
            self.__index[e['qrcode']] = i
            self.__username_index[e['username']] = i
        return True

    def __getKeyFromUsernameIndex(self,username):
        try:
            return self.__username_index[username]
        except Exception as ex:
            print("Username index not found")
            return False

    def __getKeyFromIndex(self,qrcode):
        if qrcode in self.__index:
            key = self.__index[qrcode]
            if key < self.__simpleCache.__len__():
                return key
        return False


    def updateCache(self,updated):
        print("Updating Cache...")
        self.__simpleCache = updated
        self.__cache_age = time.time()
        self.__saveCacheAsJson()
        self.createIndex()
        print("Cache updated")
        return True

    def updateCacheFromAPI(self):
        self.__initCache(force_update=True)
